package com.york.utils.client;

import com.alibaba.fastjson.JSONObject;
import com.york.utils.util.StringUtil;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

/**
 * @Description:
 * @Data: 2021/11/25 13:30
 * @Author: yangboxiong
 * @Version
 * @since JDK 1.8
 */
public class HttpClient {

    private static final Logger log = LoggerFactory.getLogger(HttpClient.class);

    private static final int CONNEC_TTIME_OUT = 15000; //设置连接主机服务器的超时时间
    private static final int READ_TIME_OUT = 50000; //设置读取远程返回的数据时间
    private static final String CHARSET = "UTF-8";
    /**
     * 普通http
     */
    public final static String HTTP_TYPE_COMMON = "1";
    /**
     * https无校验
     */
    public final static String HTTP_TYPE_AUTH_NONE = "2";
    /**
     * http单向认证
     */
    public final static String HTTP_TYPE_AUTH_SINGLE = "3";
    /**
     * http双向认证
     */
    public final static String HTTP_TYPE_AUTH_TWO = "4";

    public static final String[] contentTypes = {
            "application/json;charset=utf-8", "application/x-www-form-urlencoded;charset=utf-8", "text/xml;charset=UTF-8", "form-data"
    };

    public static final String[] methods = {
            "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
    };

    public static String doGet(String httpUrl) throws Exception {
        return doGet(httpUrl, CONNEC_TTIME_OUT, READ_TIME_OUT, null, CHARSET);
    }

    /**
     * get请求
     *
     * @param httpUrl  url地址
     * @param paramMap 参数
     * @param charset  字符编码
     * @return
     * @throws Exception
     */
    public static String doGet(String httpUrl, Map<String, Object> paramMap, String charset) throws Exception {
        httpUrl += "?";
        if (paramMap != null && paramMap.size() != 0) {
            httpUrl += buildGetParam(true, paramMap, charset);
        }
        return doGet(httpUrl, CONNEC_TTIME_OUT, READ_TIME_OUT, null, charset);
    }

    /**
     * get请求
     *
     * @param httpUrl  url地址
     * @param paramMap 请求参数
     * @param header   请求头
     * @param charset  字符编码
     * @return
     * @throws Exception
     */
    public static String doGet(String httpUrl, Map<String, Object> paramMap, Map<String, String> header, String charset) throws Exception {
        httpUrl += "?";
        if (paramMap != null && paramMap.size() != 0) {
            httpUrl += buildGetParam(true, paramMap, charset);
        }
        return doGet(httpUrl, CONNEC_TTIME_OUT, READ_TIME_OUT, header, charset);
    }

    /**
     * get请求
     *
     * @param httpUrl       url地址
     * @param connecTimeOut 超时时间
     * @param readTimeOut   读取时间
     * @param header        请求头
     * @param charset       字符编码
     * @return
     * @throws Exception
     */
    public static String doGet(String httpUrl, int connecTimeOut, int readTimeOut, Map<String, String> header, String charset) throws Exception {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        String result = null;
        try {
            if (StringUtil.isBlank(charset)) {
                charset = CHARSET;
            }

            // 创建远程url连接对象
            URL realUrl = new URL(httpUrl);
            connection = (HttpURLConnection) realUrl.openConnection();

            // trust-https
            boolean useHttps = httpUrl.startsWith("https");
            if (useHttps) {
                HttpsURLConnection https = (HttpsURLConnection) connection;
                trustAllHosts(https);
            }

            // 设置连接方式：get
            connection.setRequestMethod(methods[0]);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setConnectTimeout(connecTimeOut);
            connection.setReadTimeout(readTimeOut);
            if (header != null) {
                for (String key : header.keySet()) {
                    connection.setRequestProperty(key, header.get(key));
                }
            }
            log.info("---->请求地址:{}", URLDecoder.decode(httpUrl, charset));
            log.info("---->请求Method:{}", connection.getRequestMethod());

            // 发送请求
            connection.connect();

            // 通过connection连接，获取输入流
            if (connection.getResponseCode() == 200) {
                inputStream = connection.getInputStream();
                // 封装输入流is，并指定字符集
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charset));
                String line;
                StringBuilder resultBuilder = new StringBuilder();
                while ((line = bufferedReader.readLine()) != null) {
                    resultBuilder.append(line);
                }
                result = resultBuilder.toString();
            }

        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e2) {
                log.error(e2.getMessage(), e2);
            }
        }
        return result;
    }

    /**
     * post请求
     *
     * @param httpUrl  请求地址
     * @param reqParam 参数
     * @return 返回参数
     * @throws Exception
     */
    public static String doPost(String httpUrl, String reqParam) throws Exception {
        String contentType = contentTypes[0];
        return doRequest(httpUrl, methods[1], CONNEC_TTIME_OUT, READ_TIME_OUT, reqParam, contentType, null, CHARSET);
    }

    /**
     * post请求
     *
     * @param httpUrl     请求地址
     * @param reqParam    请求参数
     * @param header      请求头
     * @param contentType 请求类型
     * @return
     * @throws Exception
     */
    public static String doPost(String httpUrl, String reqParam, Map<String, String> header, String contentType) throws Exception {
        return doRequest(httpUrl, methods[1], CONNEC_TTIME_OUT, READ_TIME_OUT, reqParam, contentType, header, CHARSET);
    }

    /**
     * post请求
     *
     * @param httpUrl     请求地址
     * @param reqParamMap 请求参数
     * @param contentType 请求类型
     * @return
     * @throws Exception
     */
    public static String doPost(String httpUrl, Map<String, Object> reqParamMap, String contentType) throws Exception {
        String reqParam = buildGetParam(false, reqParamMap, CHARSET);
        return doRequest(httpUrl, methods[1], CONNEC_TTIME_OUT, READ_TIME_OUT, reqParam, contentType, null, CHARSET);
    }

    /**
     * post请求
     *
     * @param httpUrl     请求地址
     * @param reqParamMap 请求参数
     * @param contentType 请求类型
     * @param charset     字符编码
     * @return
     * @throws Exception
     */
    public static String doPost(String httpUrl, Map<String, Object> reqParamMap, String contentType, String charset) throws Exception {
        String reqParam = buildGetParam(false, reqParamMap, charset);
        return doRequest(httpUrl, methods[1], CONNEC_TTIME_OUT, READ_TIME_OUT, reqParam, contentType, null, CHARSET);
    }

    /**
     * post请求
     *
     * @param httpUrl     请求地址
     * @param reqParamMap 请求参数
     * @param header      请求头
     * @param contentType 请求类型
     * @return
     * @throws Exception
     */
    public static String doPost(String httpUrl, Map<String, Object> reqParamMap, Map<String, String> header, String contentType) throws Exception {
        String reqParam = buildGetParam(false, reqParamMap, CHARSET);
        return doRequest(httpUrl, methods[1], CONNEC_TTIME_OUT, READ_TIME_OUT, reqParam, contentType, header, CHARSET);
    }

    public static String doRequest(String httpUrl, String method, int connecTimeOut, int readTimeOut, String param, String contentType, Map<String, String> header, String charset) throws Exception {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;
        String result = "";
        try {
            if (StringUtil.isBlank(charset)) {
                charset = CHARSET;
            }

            URL realUrl = new URL(httpUrl);
            connection = (HttpURLConnection) realUrl.openConnection();

            // trust-https
            boolean useHttps = httpUrl.startsWith("https");
            if (useHttps) {
                HttpsURLConnection https = (HttpsURLConnection) connection;
                trustAllHosts(https);
            }

            connection.setRequestMethod(method);
            connection.setConnectTimeout(connecTimeOut);
            connection.setReadTimeout(readTimeOut);
            // 默认值为：false，当向远程服务器传送数据/写数据时，需要设置为true
            connection.setDoOutput(true);
            // 默认值为：true，当前向远程服务读取数据时，设置为true，该参数可有可无
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setRequestProperty("Content-Type", contentType);
            if (header != null && header.size() != 0) {
                for (String key : header.keySet()) {
                    connection.setRequestProperty(key, header.get(key));
                }
            }

            log.info("---->请求地址:{}", httpUrl);
            log.info("---->请求Method:{},请求格式:{}", connection.getRequestMethod(), contentType);
            log.info("---->请求主体:{}", param);
            // 通过连接对象获取一个输出流,此处getOutputStream会隐含的进行connect(即：如同调用上面的connect()方法，这里才是正式发送请求
            OutputStream outputStream = connection.getOutputStream();
            // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的
            if (!StringUtil.isBlank(param)) {
                outputStream.write(param.getBytes(charset));
                outputStream.flush();
                outputStream.close();
            }

            // 通过连接对象获取一个输入流，向远程读取,发送请求
//            if (connection.getResponseCode() == 200) {
            inputStream = connection.getInputStream();
            // 对输入流对象进行包装:charset根据工作项目组的要求来设置
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charset));
            // 循环遍历一行一行读取数据
            StringBuilder resultBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                resultBuilder.append(line);
            }
            result = resultBuilder.toString();
//            }

        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e2) {
                log.error(e2.getMessage(), e2);
            }
        }
        return result;
    }

    /**
     * @param is      是否开启URL编码
     * @param map     请求参数
     * @param charset 编码
     * @return
     */
    private static String buildGetParam(Boolean is, Map<String, Object> map, String charset) {
        StringBuffer sb = new StringBuffer();
        if (map.size() > 0) {
            for (String key : map.keySet()) {
                sb.append(key + "=");
                if (StringUtil.isEmpty(map.get(key).toString())) {
                    sb.append("&");
                } else {
                    String value = map.get(key).toString();
                    if (is) {
                        try {
                            value = URLEncoder.encode(value, charset);
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                    sb.append(value + "&");
                }
            }
        }
        return sb.toString().substring(0, sb.toString().length() - 1);
    }

    /**
     * https 不验证证书
     * 必须将hostname校验和CA证书校验同时关
     */
    private static void trustAllHosts(HttpsURLConnection connection) {
        try {
            //TLS1.0与SSL3.0基本上没有太大的差别，可粗略理解为TLS是SSL的继承者，但它们使用的是相同的SSLContext
            SSLContext sc = SSLContext.getInstance("TLS");
            //使用TrustManager来初始化该上下文，TrustManager只是被SSL的Socket所使用
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            SSLSocketFactory newFactory = sc.getSocketFactory();

            connection.setSSLSocketFactory(newFactory);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        //效验服务器主机名的合法性,这里不做校验
        connection.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
    }

    //创建TrustManager
    private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
    }};

    /**
     * 从request数据流中读取字符串
     *
     * @param request request
     * @return
     */
    public static String getPostStrFromStream(HttpServletRequest request, String charset) {
        StringBuffer buffer = new StringBuffer();
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        if (StringUtil.isBlank(charset)) {
            charset = CHARSET;
        }
        try {
            inputStream = request.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, charset);
            bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
        } catch (UnsupportedEncodingException e) {
            // SysLogger.error(class, "请求字符集不支持，获取请求数据出错");
            e.printStackTrace();
        } catch (IOException e) {
            // SysLogger.error(class, "IO异常，获取请求出错");
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                }
            }
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
        }

        return buffer.toString();
    }

    /**
     * 获取请求参数 json
     *
     * @param request 获取请求数据
     * @param charset 字符编码
     * @return
     */
    public static JSONObject getRequestParamJson(HttpServletRequest request, String charset) {
        String contentType = StringUtil.ifEmptyThen(request.getContentType(), "");
        String method = StringUtil.ifEmptyThen(request.getMethod(), "");

        JSONObject resJsonObject = null;
        String reqParam = "";
        try {

            if (StringUtil.isBlank(charset)) {
                charset = CHARSET;
            }
            if (method.equalsIgnoreCase("get") || ((method.equalsIgnoreCase("post") && !StringUtil.isBlank(contentType)))) {
                if (method.equalsIgnoreCase(methods[0]) || contentType.contains("application/x-www-form-urlencoded")) {
                    Map parameterMap = request.getParameterMap();
                    resJsonObject = StringUtil.getRequestMapValue(parameterMap);
                } else if (method.equalsIgnoreCase(methods[1]) && contentType.contains("application/json")) {
                    resJsonObject = JSONObject.parseObject(getPostStrFromStream(request, charset));
                } else if (contentType.contains("form-data")) {
                    DiskFileItemFactory factory = new DiskFileItemFactory();
                    ServletFileUpload upload = new ServletFileUpload(factory);
                    upload.setHeaderEncoding(charset);
                    List items = null;
                    try {
                        items = upload.parseRequest(request);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    resJsonObject = new JSONObject();
                    for (Object object : items) {
                        FileItem fileItem = (FileItem) object;
                        if (fileItem.isFormField()) {
                            resJsonObject.put(fileItem.getFieldName(), fileItem.getString(charset));
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("----->解析request数据异常:{}", e.getMessage());
            e.printStackTrace();
        }
        return resJsonObject;
    }

}
